Skip to content

feat: Integrate interactive Python shell into pyRevit with UI enhancements#3480

Open
romangolev wants to merge 17 commits into
developfrom
feat/revitpythonshell-inside-pyrevit
Open

feat: Integrate interactive Python shell into pyRevit with UI enhancements#3480
romangolev wants to merge 17 commits into
developfrom
feat/revitpythonshell-inside-pyrevit

Conversation

@romangolev

Copy link
Copy Markdown
Member

Description

Adds a RevitPythonShell-style interactive Python shell directly into pyRevit, so users can run Python statements against the live Revit API without leaving Revit. Ships as a new pyRevitLabs.PyRevit.Shell assembly plus a Python Shell pushbutton on the pyRevit panel.

Key pieces:

  • REPL + editor UI — AvalonEdit-based console with history, completion popups, syntax highlighting, plus an optional code editor pane, themed with fluent glyphs and custom title bars.
  • Multiple open modes — Modal, Modeless, Modal Editor, Modeless Editor, Docked, Docked Editor.
    Dockable variants register through f the console builds lazily on firstshow via an Idling handler. Mode is configurable via Shift+Click on the pushbutton.
  • Theme matching — detects the activlight/dark to match.
  • Runtime integration — statements execute through an ExternalEvent; pyrevit/revit/events.py now
    creates its module-level ExternalEveom the shell's REPL thread isn'talways a valid API context.
  • Dev host — pyRevitLabs.PyRevit.Sheable (excluded from CI/product build) for iterating on the shell UI outside Revit.
  • Build pipeline — BuildShellModule.cs deploys pyRevitLabs.PyRevit.Shell.dll alongside each IronPython engine.

Additional Notes

  • Dockable pane ID is shared/hardcoded between pyRevitCore.extension/startup.py and the pushbutton's script.py — keep in sync if changed.
  • Requires closing Revit before rebuilding the shell assembly (locked engine DLLs).

romangolev added 17 commits June 7, 2026 05:47
- Create pyRevitLabs.PyRevit.Shell, an AvalonEdit-based WPF interactive Python console

- Integrate shell compilation into dev scripts, labs build system, and command line utility

- Add a new 'Python Shell' pulldown button under the pyRevit panel with Modal and Modeless shell launchers
Host the IronPython console as a pyRevit dockable pane so it stays open alongside the model. The pyRevitLabs.PyRevit.Shell assembly now exposes CreateDockableConsole, which builds a fully-wired IronPythonConsoleControl configured with the full pyRevit environment and marshals each statement through an ExternalEvent so Revit stays interactive while the pane is open. pyRevitCore registers the panel at startup with the same lifecycle as other pyRevit dockable panes.
Remove prebuilt pyRevitLabs.PyRevit.Shell.dll and ICSharpCode.AvalonEdit.dll from every engine folder under bin/. These are generated product binaries; the same pattern was used previously for pyRevitLabs.CommonWPF.dll.
Add BuildShellModule that builds pyRevitLabs.PyRevit.Shell for both IPY2712PR and IPY342 after BuildRuntimeModule. The csproj's DeployShell target copies pyRevitLabs.PyRevit.Shell.dll and ICSharpCode.AvalonEdit.dll into each engine folder, replacing the prebuilt binaries removed previously.
Introduce RevitThemeDetector (reflection-based so the same assembly compiles against pre-2024 Revits where UIThemeManager does not exist) and apply the detected theme to the console control and window/pane background across all three shell entry points: ShowModal, ShowModeless, and the dockable console. The dockable panel applies the same theme to its own background so the shell blends with the surrounding Revit chrome.
Update both the light and dark variants of the Python Shell pulldown icons.
Replace the Python Shell pulldown (Modal / Modeless / Dockable sub-buttons) with a single pushbutton. Shift+Click opens a small picker that persists the chosen mode in the script config; the next launch opens the shell as Modal, Modeless, or as the dockable pane accordingly. Icons move with the button.

Rework the dockable pane registration so it fits the opt-in flow:
- Pane now defaults to hidden; on the next Idling tick after startup the pane is re-hidden, since Revit persists DockablePane visibility across sessions and would otherwise reopen a pane the user never asked for.
- The console is no longer built during Loaded. An Idling handler subscribed at registration time builds it once the pane has actually been shown, so the pyRevitLabs.PyRevit.Shell assembly is loaded on demand rather than at startup.
- HOST_APP.uiapp is captured at startup (while the __revit__ builtin is still resolvable) and reused for late console creation.
- Build failures now render the traceback inside the pane instead of leaving an empty surface with only a log entry.
Completion windows are created on demand by AvalonEdit, so they were always rendered with the default WPF light styling - jarring against a dark-themed console. Plumb the current theme from PythonConsoleControl through PythonConsolePad to PythonTextEditor, and have each freshly-created PythonConsoleCompletionWindow recolor its list, tooltip, and selection brushes when the dark theme is active. Light theme falls through to the default brushes.
…dows

Add an EditorView (AvalonEdit text editor with Python syntax highlighting, file save and load, and F5 to run) paired with the existing IronPython console. Run sends the current selection, or the whole document, to the REPL through the same engine and dispatch path as the console-only shell. Two new shell entry points drive it: Shell.ModalEditor (Revit blocked, transaction can span several statements) and Shell.ModelessEditor (Revit stays interactive, dispatch via the existing ExternalEvent).

ShellLauncher becomes generic over the window type via a new internal IShellWindow interface (ConsoleControl, ApplyTheme, SetRevitAsWindowOwner), so both the console-only and editor windows share the modal and modeless plumbing without duplication. InteractiveShellWindow now implements IShellWindow. The Python Shell button picker grows two options (Modal Editor, Modeless Editor) and the launcher script dispatches to the new entry points.
CreateDockableEditor was unusable: ConfigureEngineViaRuntime reaches InjectBuiltins, which reads the Ribbon via ComponentManager, and the Ribbon is owned by Revit's main UI thread - calling it from the REPL dispatcher raised InvalidOperationException. Marshal engine setup onto the main thread captured at launch (already the pattern for the existing shell entry points) and unwind TargetInvocationException via DescribeSetupError so the real failure (type, message, stack) shows in the REPL instead of a generic wrapper. Extract ConfigureControl so dockable and window-based shells share dispatch wiring, and add EnsureInteractiveBuiltins so reserved pyrevit builtins (e.g. __shiftclick__, __window__) get safe defaults if setup runs off-thread or fails.

pyrevit.revit.events had a related bug: UI.ExternalEvent.Create needs a standard API context, but the module used to call it at import time. Importing it from the interactive shell (REPL runs statements on a non-command thread) crashed before the REPL could even start. Wrap the two top-level Create calls in try/except and replace them with lazy _get_unregisterer_extevent / _get_execute_extevent helpers that retry on first use.

Wire the new variant into the UI: the Python Shell pushbutton picker grows a Docked Editor option, the launcher script recognises it, and pyRevitCore.startup now reads the same mode setting to pick CreateDockableEditor (or CreateDockableConsole) when the pane is first shown.
Mirrors ShellLauncher's modal path but drops the Revit-specific pieces
(UIApplication, ExternalEvent, runtime-builtin injection) so the REPL
and editor can be iterated on without starting Revit.

The host references the already-deployed shell DLL from
bin/netcore/engines/IPY2712PR/, is not referenced by any shipped
solution or the build pipeline, and is therefore never built in CI or
shipped to end users. Also wires a local-only appsettings.Development.json
override into the build (gitignored) for engine/runtime tweaks during dev.
Replace the text-only toolbar items with Segoe Fluent Icons glyphs (with
Win10 MDL2 fallback) plus labels, and add light/dark theme brush resources
that are swapped at runtime by ApplyTheme alongside the editor and console
palettes. Keeps the toolbar in sync with the rest of the shell's theme
without bitmap assets.
Extract the toolbar brushes, control styles, and scrollbar templates into
a shared ShellTheme resource dictionary plus a static ShellTheme.Apply
helper that swaps the light/dark palette on any root. Both shell windows
now merge ShellTheme.xaml, so a single Apply call keeps the editor,
console, toolbar, scrollbars, and title bar in sync with the active
Revit UI theme.

Also drop the default chrome on InteractiveShellWindow /
InteractiveEditorWindow and host a new ShellTitleBar UserControl
(pyRevit icon, draggable, double-click to maximize, themed min/max/close
buttons) so the borderless windows follow the shell palette instead of
the OS caption. Stock overflow on the toolbar is collapsed on Loaded
since the shell never overflows.

@devloai devloai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary:

  • Adds a full RevitPythonShell-style interactive Python shell (pyRevitLabs.PyRevit.Shell) with an AvalonEdit-based REPL/editor, history, completion, syntax highlighting and theme matching.
  • Ships a new "Python Shell" pushbutton on the pyRevit panel with 6 open modes (Modal/Modeless/Docked × console/editor), plus a config.py to pick the mode.
  • Wires statement execution through ExternalEvent so Revit stays interactive; hardens pyrevit/revit/events.py to tolerate ExternalEvent.Create being called outside a valid API context (e.g. from the shell's REPL thread).
  • Adds build pipeline support (BuildShellModule.cs) to deploy the shell DLL alongside each IronPython engine, and a dev-only host for iterating outside Revit.

Review Summary:

Reviewed the C# shell assembly, the events.py hardening, and the new/edited Python startup/pushbutton scripts against this repo's guidelines (IronPython 2.7 target compatibility, black/PEP8 formatting, and general code-review checklist patterns from prior PRs — e.g. silent-failure logging in initialization paths, ExternalEvent/queue handler design, vendored-dependency licensing conventions).

The most important finding: several new/modified Python files (events.py, startup.py, and the new pushbutton script.py/config.py) were saved with a leading UTF-8 BOM, which IronPython 2.7 does not strip — this can throw a SyntaxError at import/execution time and should be fixed before merge. I also flagged a concurrency hazard in ShellLauncher.cs where a non-thread-safe Queue<Action> and a single shared AutoResetEvent are used by multiple independently-running threads (REPL execution, paste/editor "Run", and background autocomplete), a silently-dropped scheduled callback in events.py when ExternalEvent creation fails, a missing blank line between two method defs (black/PEP8), and a missing license file for the vendored console-control code (consistent with this repo's licenses/ convention for every other vendored dependency).

Overall the shell architecture itself (lazy dockable-pane build via Idling, ExternalEvent-based dispatch, theme detection) is well designed; the issues above are concrete and actionable rather than architectural concerns.

Suggestions

  • Re-save events.py, startup.py, script.py, and config.py as BOM-less UTF-8. Apply
  • Add a licenses/ file attributing the vendored PythonConsoleControl (Joe Moorhouse) code. Apply

Apply all quick fixes (5 quick fixes)

@@ -1,4 +1,4 @@
"""Revit events handler management."""
"""Revit events handler management."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A UTF-8 BOM (\ufeff) was accidentally introduced at the very start of this file (visible as the invisible character before """Revit events handler management."""). IronPython 2.7 does not strip a leading BOM when parsing .py source (unlike CPython 3), so a module imported this way can raise SyntaxError: invalid syntax on the first line. Since all pyRevit runtime code targets IronPython 2.7 per repo convention, this needs to be removed.

Please re-save this file as plain UTF-8 without signature (no BOM). The same issue appears in extensions/pyRevitCore.extension/startup.py, .../Python Shell.pushbutton/script.py and .../Python Shell.pushbutton/config.py — please check and fix those too.

Apply quick fix

actions

Quick Fix: Apply a small focused fix for this specific issue (takes < 1min).

Feedback: Rate this comment to help me improve future code reviews:

  • 👍 Good - Helpful and accurate
  • 👎 Poor - Wrong, unclear, or unhelpful
  • Skip if you don't have any strong opinions either way.

@@ -1,8 +1,12 @@
"""pyRevit core startup script"""
"""pyRevit core startup script"""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same leading UTF-8 BOM issue as pyrevitlib/pyrevit/revit/events.py — this file starts with an invisible \ufeff before the docstring. This is a core startup script executed on every pyRevit session load; a BOM here can break IronPython 2.7 parsing (SyntaxError on import) since IronPython 2.7 doesn't auto-strip BOMs like CPython 3 does. Please re-save without the BOM.

Apply quick fix

actions

Quick Fix: Apply a small focused fix for this specific issue (takes < 1min).

Feedback: Rate this comment to help me improve future code reviews:

  • 👍 Good - Helpful and accurate
  • 👎 Poor - Wrong, unclear, or unhelpful
  • Skip if you don't have any strong opinions either way.

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new file also starts with a UTF-8 BOM (\ufeff) before the # -*- coding: utf-8 -*- comment. Same concern as noted on events.py/startup.py: IronPython 2.7 doesn't strip BOMs, so this can crash with a syntax error when the button is clicked. config.py in this same bundle has the identical issue. Please re-save both as BOM-less UTF-8.

Apply quick fix

actions

Quick Fix: Apply a small focused fix for this specific issue (takes < 1min).

Feedback: Rate this comment to help me improve future code reviews:

  • 👍 Good - Helpful and accurate
  • 👎 Poor - Wrong, unclear, or unhelpful
  • Skip if you don't have any strong opinions either way.

Comment on lines +249 to +263
public class ShellExternalEventDispatcher : IExternalEventHandler {
readonly IronPythonConsoleControl _consoleControl;
readonly Queue<Action> _commands = new Queue<Action>();
readonly AutoResetEvent _commandCompleted;

public ShellExternalEventDispatcher(IronPythonConsoleControl consoleControl, AutoResetEvent commandCompleted) {
_consoleControl = consoleControl;
_commandCompleted = commandCompleted;
}

public void Enqueue(Action command) {
_commands.Enqueue(command);
}

public void Execute(UIApplication app) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ShellExternalEventDispatcher uses a plain (non-thread-safe) Queue<Action> plus a single shared AutoResetEvent (commandCompleted), and the same dispatch closure/handler instance is wired (in ConfigureControl/AttachAndDispatch, lines ~115-121) to both host.Console.SetCommandDispatcher(dispatch) and host.Editor.SetCompletionDispatcher(dispatch).

SetCommandDispatcher installs dispatch as IronPython's own command dispatcher (PythonContext.GetSetCommandDispatcher), which is invoked from the console host's background REPL read-eval thread for typed statements, and also from PythonConsole.ExecuteStatements() (on PythonConsole's dedicated dispatcherThread) for pasted/multi-line and editor "Run" statements. SetCompletionDispatcher is invoked from PythonTextEditor's independent background completion thread (PythonTextEditor.Completion()BackgroundShowCompletionWindow/BackgroundUpdateCompletionDescription).

So up to three independently-running threads can call the same dispatch concurrently (e.g. autocomplete firing while a pasted block or editor run is still executing). Two threads calling handler.Enqueue(...) at the same time on the same Queue<Action> is unsafe (Queue<T> is not thread-safe for concurrent writers) and can corrupt internal state or throw. Separately, because commandCompleted is shared, AutoResetEvent.Set() from Execute() can release any currently-waiting caller, not necessarily the one whose command actually finished — so a completion-dispatch caller could return from WaitOne() before its own action has run (or vice-versa), racing with the actual work.

Suggest either (a) a thread-safe collection (ConcurrentQueue<Action>) with a per-call synchronization primitive (queue (Action, ManualResetEventSlim) tuples and wait on the item's own handle), or (b) giving completion its own dedicated ExternalEvent/handler pair so it can't interleave with statement execution on the shared queue.

actions

Feedback: Rate this comment to help me improve future code reviews:

  • 👍 Good - Helpful and accurate
  • 👎 Poor - Wrong, unclear, or unhelpful
  • Skip if you don't have any strong opinions either way.

Comment on lines 279 to +282
_HANDLER.schedule(_wrapper)
_EXTERNAL_EVENT.Raise()
_ext_event = _get_execute_extevent()
if _ext_event is not None:
_ext_event.Raise()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If _get_execute_extevent() returns None (i.e. ExternalEvent.Create failed both at import time and on this lazy retry), _wrapper has already been pushed onto _HANDLER._queue on the previous line, but Raise() is never called. The queued closure — and whatever func/args/kwargs it closed over — just sits in the queue forever with no execution and no error surfaced to the caller. execute_in_revit_context() silently returns as if it scheduled work.

Per repo convention ("silent failures in initialization/critical paths must be logged"), this should at minimum log an error so callers can diagnose why their scheduled callback never ran, e.g.:

_ext_event = _get_execute_extevent()
if _ext_event is not None:
    _ext_event.Raise()
else:
    mlogger.error("Could not create ExternalEvent; scheduled callback will not run")

Same applies to delayed_unregister_exec_handlers above.

Apply quick fix

actions

Quick Fix: Apply a small focused fix for this specific issue (takes < 1min).

Feedback: Rate this comment to help me improve future code reviews:

  • 👍 Good - Helpful and accurate
  • 👎 Poor - Wrong, unclear, or unhelpful
  • Skip if you don't have any strong opinions either way.

@@ -0,0 +1,150 @@
// Copyright (c) 2010 Joe Moorhouse

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file (and most of Console/*.cs in this new project) carries a // Copyright (c) 2010 Joe Moorhouse header, indicating it's vendored/ported from a third-party project (the IronPython console control originally from SharpDevelop/RevitPythonShell). Every other vendored dependency in this repo ships an accompanying license file under licenses/ (e.g. PYTHONNET_LICENSE.txt, MAHAPPS.METRO_LICENSE.txt). Please add the upstream license (and ideally a pointer to the source repo) for this ported console control, and likewise for the bundled ICSharpCode.AvalonEdit dependency if not already covered, to keep attribution consistent with repo convention.

actions

Feedback: Rate this comment to help me improve future code reviews:

  • 👍 Good - Helpful and accurate
  • 👎 Poor - Wrong, unclear, or unhelpful
  • Skip if you don't have any strong opinions either way.

Comment on lines +144 to +145
)
def _attach_console(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing blank line between _apply_panel_theme and _attach_console method definitions — def _attach_console(self): immediately follows the closing ) of the previous method with no separating blank line. Repo convention (black/PEP8) requires a blank line between method definitions inside a class body. Please add a blank line before def _attach_console(self):.

Apply quick fix

actions

Quick Fix: Apply a small focused fix for this specific issue (takes < 1min).

Feedback: Rate this comment to help me improve future code reviews:

  • 👍 Good - Helpful and accurate
  • 👎 Poor - Wrong, unclear, or unhelpful
  • Skip if you don't have any strong opinions either way.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a RevitPythonShell-style interactive Python REPL (with optional AvalonEdit editor) integrated directly into pyRevit, including a new shell assembly, a ribbon pushbutton to launch it in multiple modes (modal/modeless/docked), and supporting runtime/build pipeline updates so commands execute in a valid Revit API context.

Changes:

  • Adds a new pyRevitLabs.PyRevit.Shell WPF-based interactive shell (console + optional editor) with theme matching and ExternalEvent-based dispatch for modeless/docked scenarios.
  • Registers a new dockable panel at startup and adds a new “Python Shell” pushbutton with a Shift+Click configuration UI.
  • Extends dev/build tooling to build and deploy the shell per IronPython fork and to expose runtime engine configuration for interactive usage.

Reviewed changes

Copilot reviewed 50 out of 54 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
pyrevitlib/pyrevit/revit/events.py Makes ExternalEvent creation tolerant to non-API import contexts and defers creation/raising logic.
extensions/pyRevitCore.extension/startup.py Registers a dockable “pyRevit Python Shell” panel and lazily builds the hosted shell control on first show.
extensions/pyRevitCore.extension/PythonShellDockablePanel.xaml Adds a minimal dockable pane host surface for the shell control.
extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Python Shell.pushbutton/script.py Adds pushbutton script to open the shell in modal/modeless/docked (+ editor) modes.
extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Python Shell.pushbutton/config.py Adds Shift+Click configuration UI for selecting shell open mode.
extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Python Shell.pushbutton/bundle.yaml Defines UI metadata (title/tooltip/context) for the new pushbutton.
extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/bundle.yaml Adds “Python Shell” into the pyRevit panel layout.
dev/scripts/configs.py Adds a dev path constant for the shell project.
dev/pyRevitLabs.PyRevit.Shell/Themes/ShellTheme.xaml Introduces shared theme brushes and control styles for shell UI chrome/scrollbars.
dev/pyRevitLabs.PyRevit.Shell/Themes/ShellTheme.cs Adds runtime theme brush swapping for light/dark palettes.
dev/pyRevitLabs.PyRevit.Shell/ShellTitleBar.xaml.cs Implements custom draggable title bar behavior for borderless shell windows.
dev/pyRevitLabs.PyRevit.Shell/ShellTitleBar.xaml Defines the title bar UI with minimize/maximize/close buttons and icon.
dev/pyRevitLabs.PyRevit.Shell/ShellLauncher.cs Implements shell window/control creation and Revit-context dispatching via ExternalEvent.
dev/pyRevitLabs.PyRevit.Shell/Shell.cs Exposes public entry points for launching shell windows and creating dockable controls.
dev/pyRevitLabs.PyRevit.Shell/RevitThemeDetector.cs Detects Revit’s current UI theme via reflection for compatibility across versions.
dev/pyRevitLabs.PyRevit.Shell/pyRevitLabs.PyRevit.Shell.csproj Adds the new shell project targeting net48 and net8.0-windows with embedded resources.
dev/pyRevitLabs.PyRevit.Shell/InteractiveShellWindow.xaml.cs Hosts the REPL-only shell window and applies theme/owner behavior.
dev/pyRevitLabs.PyRevit.Shell/InteractiveShellWindow.xaml Defines the shell window (borderless) layout for the console-only variant.
dev/pyRevitLabs.PyRevit.Shell/InteractiveEditorWindow.xaml.cs Hosts the editor+REPL window and applies theme/owner behavior.
dev/pyRevitLabs.PyRevit.Shell/InteractiveEditorWindow.xaml Defines the shell window layout for the editor+console variant.
dev/pyRevitLabs.PyRevit.Shell/EditorView.xaml.cs Implements AvalonEdit editor actions (open/save/run) and theme synchronization with console.
dev/pyRevitLabs.PyRevit.Shell/EditorView.xaml Defines editor+console UI (toolbar, editor, splitter, console).
dev/pyRevitLabs.PyRevit.Shell/Directory.Build.targets Adds per-IronPython-fork build configurations and deploys shell + AvalonEdit into engine folders.
dev/pyRevitLabs.PyRevit.Shell/Console/Resources/Python.xshd Adds a light syntax-highlighting definition for Python.
dev/pyRevitLabs.PyRevit.Shell/Console/Resources/Python-Dark.xshd Adds a dark syntax-highlighting definition for Python.
dev/pyRevitLabs.PyRevit.Shell/Console/PythonTextEditor.cs Adapts/extends console editor behaviors including themed completion popups.
dev/pyRevitLabs.PyRevit.Shell/Console/PythonOutputStream.cs Writes engine output into the console editor and decodes unicode escapes.
dev/pyRevitLabs.PyRevit.Shell/Console/PythonEditingCommandHandler.cs Implements editing command behaviors (cut/copy/delete) for the console editor.
dev/pyRevitLabs.PyRevit.Shell/Console/PythonConsolePad.cs Wraps console host/control and applies theme settings (including completion theme).
dev/pyRevitLabs.PyRevit.Shell/Console/PythonConsoleHost.cs Hosts the IronPython console runtime and wires output/engine initialization.
dev/pyRevitLabs.PyRevit.Shell/Console/PythonConsoleHighlightingColorizer.cs Adds a custom colorizer that distinguishes input vs output lines.
dev/pyRevitLabs.PyRevit.Shell/Console/PythonConsoleControl.xaml.cs Implements WPF control hosting and theme/highlighting application logic.
dev/pyRevitLabs.PyRevit.Shell/Console/PythonConsoleControl.xaml Defines the WPF wrapper control for the embedded console editor.
dev/pyRevitLabs.PyRevit.Shell/Console/PythonConsoleCompletionWindow.cs Implements completion UI and applies dark theme styling to list/tooltip.
dev/pyRevitLabs.PyRevit.Shell/Console/PythonConsoleCompletionDataProvider.cs Provides completion items/descriptions, including fork-agnostic iteration behavior.
dev/pyRevitLabs.PyRevit.Shell/Console/PythonConsole.cs Implements the interactive console behaviors, history, dispatching, and statement execution.
dev/pyRevitLabs.PyRevit.Shell/Console/PythonCompletionData.cs Defines AvalonEdit completion item entries.
dev/pyRevitLabs.PyRevit.Shell/Console/CommandLineHistory.cs Implements command history navigation for the console.
dev/pyRevitLabs.PyRevit.Shell.DevHost/README.md Documents the dev-only host app for iterating on the shell outside Revit.
dev/pyRevitLabs.PyRevit.Shell.DevHost/pyRevitShellDevHost.csproj Adds a dev-only WinExe project referencing deployed engine DLLs with optional pre-build shell build.
dev/pyRevitLabs.PyRevit.Shell.DevHost/Program.cs Adds a standalone host executable that launches the shell UI outside Revit for UI iteration.
dev/pyRevitLabs.PyRevit.Runtime/IronPythonEngine.cs Refactors builtins injection into a reusable method for interactive shell configuration.
dev/pyRevitLabs.PyRevit.Runtime/InteractiveEngine.cs Adds a runtime entry point for configuring an interactive engine like a normal pyRevit run.
dev/pyrevit.py Adds a pyrevit build shell CLI target.
dev/_labs.py Implements the build_shell CLI command to build per-fork configurations.
dev/_build.py Ensures shell build runs as part of the dev “build binaries” sequence.
build/Program.cs Registers the new BuildShellModule into the build pipeline.
build/Modules/BuildShellModule.cs Adds a build module to build/deploy the shell after runtime builds.
build/Helpers/PyRevitPaths.cs Adds a path helper for the shell project.
build/Build.csproj Copies Development appsettings to output (for local overrides).
.gitignore Ignores local-only build/appsettings.Development.json.

Comment on lines 279 to +282
_HANDLER.schedule(_wrapper)
_EXTERNAL_EVENT.Raise()
_ext_event = _get_execute_extevent()
if _ext_event is not None:
_ext_event.Raise()
Comment on lines 114 to +118
def delayed_unregister_exec_handlers(handler_group_id):
HANDLER_UNREGISTERER.handler_group_id = handler_group_id
HANDLER_UNREGISTERER_EXTEVENT.Raise()
ext_event = _get_unregisterer_extevent()
if ext_event is not None:
ext_event.Raise()
Comment on lines +22 to +39
def _load_shell():
# The shell DLL ships into the active engine folder (the IronPython fork pyRevit runs),
# alongside the loaded pyRevitLoader; loading it from there makes the shell use that engine.
engine_dir = None
for asm in AppDomain.CurrentDomain.GetAssemblies():
if asm.GetName().Name == "pyRevitLoader":
engine_dir = Path.GetDirectoryName(asm.Location)
break
clr.AddReferenceToFileAndPath(
Path.Combine(engine_dir, "pyRevitLabs.PyRevit.Shell.dll")
)
from PyRevitLabs.PyRevit.Shell import Shell

# Forward this engine's sys.path so `from pyrevit import ...` resolves in the shell as here.
search_paths = List[str]()
for path in sys.path:
search_paths.Add(path)
return Shell, search_paths
Comment on lines +259 to +279
public void Enqueue(Action command) {
_commands.Enqueue(command);
}

public void Execute(UIApplication app) {
while (_commands.Count > 0) {
var command = _commands.Dequeue();
try {
command();
}
catch (Exception ex) {
_consoleControl.WithConsoleHost(host => {
var formatter = host.Engine.GetService<ExceptionOperations>();
host.Console.WriteLine(formatter.FormatException(ex), Style.Error);
});
}
finally {
_commandCompleted.Set();
}
}
}
Comment on lines +235 to +242
// Poll the dispatcher operation so a Ctrl+C keyboard interrupt can break a long run.
static void RunOnDispatcher(Dispatcher dispatcher, Action command) {
if (command == null)
return;
var operation = dispatcher.BeginInvoke(DispatcherPriority.Normal, command);
while (operation.Status != DispatcherOperationStatus.Completed)
operation.Wait(TimeSpan.FromSeconds(1));
}
Comment on lines +31 to +39
if (!File.Exists(Path.Combine(engineDir, "pyRevitLabs.PyRevit.Shell.dll")))
{
Console.Error.WriteLine(
"The shell is not built yet. Build it first, e.g.:" + Environment.NewLine +
" cd build && dotnet run -c Release -- ci" + Environment.NewLine +
"or just the shell:" + Environment.NewLine +
" dotnet build dev/pyRevitLabs.PyRevit.Shell/pyRevitLabs.PyRevit.Shell.csproj -c \"Release IPY2712PR\"");
return 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants